【STM32F4系列】【HAL库】【自制库】模拟IIC主机 您所在的位置:网站首页 gpio模拟i2c从机 冲突检测 【STM32F4系列】【HAL库】【自制库】模拟IIC主机

【STM32F4系列】【HAL库】【自制库】模拟IIC主机

2023-09-04 13:55| 来源: 网络整理| 查看: 265

介绍

本项目是利用GPIO模拟I2C的主机

网上常见的是模拟I2C主机

本项目是作为一个两个单片机之间低速通信的用法

协议介绍请看,传送门

模拟从机请看这里

主机 功能描述 I2C按字节(Byte)读写I2C读写寄存器I2C连续读写 编程思路解析

主机是时钟信号的发起方,起始和中止信号均来自主机

I2C的主机编程相对简单

我们只需要按照协议发送信号即可,具体在实现各个功能时细说

HAL初始化

I2C主机需要使用2个开漏上拉输出的GPIO

分别是SDA 和 SCL

我们对其操作时序,模拟出I2C协议即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oznuyZH2-1672321697833)(图片/1.png)]

程序设计 GPIO控制

将HAL库的GPIO控制的函数做了一个封装,用于改变GPIO时的快速使用

#define I2C_SCL_GPIOx GPIOA #define I2C_SCL_Pin GPIO_PIN_0 #define I2C_SDA_GPIOx GPIOA #define I2C_SDA_Pin GPIO_PIN_1 /** * @brief 一段延迟 * @param 无 * @return 无 * @author HZ12138 * @date 2022-07-27 08:53:30 */ void I2C_Delay(void) { int z = 0xff; while (z--) ; } /** * @brief 写SDA * @param H_L:高低电平 * @return 无 * @author HZ12138 * @date 2022-10-21 18:07:18 */ void I2C_Write_SDA(GPIO_PinState H_L) { HAL_GPIO_WritePin(I2C_SDA_GPIOx, I2C_SDA_Pin, H_L); } /** * @brief 写SCL * @param H_L:高低电平 * @return 无 * @author HZ12138 * @date 2022-10-21 18:07:40 */ void I2C_Write_SCL(GPIO_PinState H_L) { HAL_GPIO_WritePin(I2C_SCL_GPIOx, I2C_SCL_Pin, H_L); } /** * @brief 读取SDA * @param 无 * @return SDA的状态 * @author HZ12138 * @date 2022-10-21 18:07:56 */ uint16_t I2C_Read_SDA(void) { return HAL_GPIO_ReadPin(I2C_SDA_GPIOx, I2C_SDA_Pin); } /** * @brief 读取SCL * @param 无 * @return SDA的状态 * @author HZ12138 * @date 2022-10-21 18:07:56 */ uint16_t I2C_Read_SCL(void) { return HAL_GPIO_ReadPin(I2C_SCL_GPIOx, I2C_SCL_Pin); } 启动中止信号

I2C的启动中止信号是在SCL高电平时,SDA发生跳变

这样只需要先拉高SCL,再改变SDA即可

必要的地方加入了延迟,避免过高的速度出现问题

/** * @brief 产生I2C起始信号 * @param 无 * @return 无 * @author HZ12138 * @date 2022-07-27 08:54:48 */ void I2C_Start(void) { I2C_Write_SDA(GPIO_PIN_SET); // 需在SCL之前设定 I2C_Write_SCL(GPIO_PIN_SET); // SCL->高 I2C_Delay(); // 延时 I2C_Write_SDA(GPIO_PIN_RESET); // SDA由1->0,产生开始信号 I2C_Delay(); // 延时 I2C_Write_SCL(GPIO_PIN_RESET); // SCL->低 } /** * @brief 产生I2C结束信号 * @param 无 * @return 无 * @author HZ12138 * @date 2022-07-27 08:57:03 */ void I2C_End(void) { I2C_Write_SDA(GPIO_PIN_RESET); // 在SCL之前拉低 I2C_Write_SCL(GPIO_PIN_SET); // SCL->高 I2C_Delay(); // 延时 I2C_Write_SDA(GPIO_PIN_SET); // SDA由0->1,产生结束信号 I2C_Delay(); // 延时 } 应答信号

对于主机来说,有两种应答,

一个是自己发送应答信号,主机控制SDA和SCL

另一个是接收从机的应答信号,主机控制SCL,从机控制SDA

这边分成了2个控制函数

注意:ACK信号在很多设备中会被省略,这边主机只保证有第9个周期即可,不用过多关注应答

/** * @brief 发送应答码 * @param ack:0 应答 1 不应达 * @return 无 * @author HZ12138 * @date 2022-07-27 09:03:38 */ void I2C_Send_ACK(uint8_t ack) { if (ack == 1) I2C_Write_SDA(GPIO_PIN_SET); // 产生应答电平 else I2C_Write_SDA(GPIO_PIN_RESET); I2C_Delay(); I2C_Write_SCL(GPIO_PIN_SET); // 发送应答信号 I2C_Delay(); // 延时至少4us I2C_Write_SCL(GPIO_PIN_RESET); // 整个期间保持应答信号 } /** * @brief 接受应答码 * @param 无 * @return 应答码 0 应答 1 不应达 * @author HZ12138 * @date 2022-07-27 09:04:28 */ uint8_t I2C_Get_ACK(void) { uint8_t ret; // 用来接收返回值 I2C_Write_SDA(GPIO_PIN_SET); // 电阻上拉,进入读 I2C_Delay(); I2C_Write_SCL(GPIO_PIN_SET); // 进入应答检测 I2C_Delay(); // 至少延时4us ret = I2C_Read_SDA(); // 保存应答信号 I2C_Write_SCL(GPIO_PIN_RESET); return ret; } 写一个字节

控制SDA向外部发送1Byte数据

从高位到低位发送,SDA的电平改变需要在SCL低电平时

发送8个bit后,第9个周期需要读取ASK信号

所以编程思路如下:

搞一个循环,从0到7做计数,每次发送数据的最高位,并将数据向右移动一位,同时在SDA改变后发送一个SCL周期

在发送完成后,读取ASK信号并保存下来

/** * @brief I2C写1Byte * @param dat:1Byte数据 * @return 应答结果 0 应答 1 不应达 * @author HZ12138 * @date 2022-07-27 09:05:14 */ uint8_t I2C_SendByte(uint8_t dat) { uint8_t ack; for (int i = 0; i uint8_t ret = 0; I2C_Write_SDA(GPIO_PIN_SET); for (int i = 0; i ret++; } I2C_Write_SCL(GPIO_PIN_RESET); I2C_Delay(); } I2C_Send_ACK(ack); return ret; } 连续写

用于写大量数据给从机

流程:

发送起始信号发送器件地址(最低位是0)发送寄存器地址将缓冲区数据按顺序发送出去发送结束信号 /** * @brief I2C连续写 * @param addr:器件地址 * @param reg:寄存器地址 * @param len:长度 * @param buf:缓冲区地址 * @return 状态 0成功 其他失败 * @author HZ12138 * @date 2022-08-08 15:47:11 */ uint8_t I2C_Write_Len(uint8_t reg, uint8_t len, uint8_t *buf) { uint8_t i; I2C_Start(); I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令 I2C_SendByte(reg); // 写寄存器地址 for (i = 0; i I2C_Start(); I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令 I2C_SendByte(reg); // 写寄存器地址 I2C_Start(); I2C_SendByte(I2C_Address | 1); // 发送器件地址+读命令 while (len) { if (len == 1) *buf = I2C_ReadByte(1); // 读数据,发送nACK else *buf = I2C_ReadByte(0); // 读数据,发送ACK len--; buf++; } I2C_End(); // 产生一个停止条件 return 0; } 写寄存器

写寄存器的一个字节

流程:

发送起始信号发送器件地址(最低位是0)发送寄存器地址将缓冲区数据发送出去发送结束信号 /** * @brief I2C写一个字节 * @param reg:寄存器地址 * @param data:数据 * @return 状态 0成功 其他失败 * @author HZ12138 * @date 2022-08-08 15:47:11 */ uint8_t I2C_Write_Reg(uint8_t reg, uint8_t data) { I2C_Start(); I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令 I2C_SendByte(reg); // 写寄存器地址 I2C_SendByte(data); // 发送数据 I2C_End(); return 0; } 读寄存器

读取寄存器的一个字节

流程:

发送起始信号发送器件地址(最低为为0)发送寄存器地址发送起始信号发送器件地址(最低为为1)读取数据写入缓冲区发送结束信号 /** * @brief I2C读一个字节 * @param reg:寄存器地址 * @return 读取到的数据 * @author HZ12138 * @date 2022-08-08 15:47:11 */ uint8_t I2C_Read_Reg(uint8_t reg) { uint8_t res; I2C_Start(); I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令 I2C_SendByte(reg); // 写寄存器地址 I2C_Start(); I2C_SendByte(I2C_Address | 1); // 发送器件地址+读命令 res = I2C_ReadByte(1); // 读取数据,发送nACK I2C_End(); // 产生一个停止条件 return res; } 成品

GitHub



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有